#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

#include <vector>

#include <process.h>
#include <strhelper.h>
#include <log.h>
#include <foreach.h>
#include <debugger.h>
#include <signalhelper.h>

#define DEF_BUF_SIZE            8192

#define GDB_PROMPT              "(gdb)"

#define GDB_PROMPT_LEN          (sizeof(GDB_PROMPT) - 1)

static char                    *_buf;

static int                      _buf_size;

static int                      _buf_pos;

/* 读标准输出，直到遇到 (gdb) 提示符，去掉提示符后面的空格与换行符。
 */

static int
_get_debugger_output(Process& proc, std::string& result)
{
    if (_buf == NULL) {
        _buf_size = DEF_BUF_SIZE;
        _buf      = (char *) malloc (_buf_size);
        _buf_pos  = 0;

        _buf[0] = 0;
    }

    int  rbytes;
    bool done;

    done = false;

    while (!done) {
        int size = _buf_size - _buf_pos - 1;
        if (size > 0) {
            rbytes = proc.read_stdout(_buf + _buf_pos, size);
            if (rbytes > 0) {
                _buf_pos       += rbytes;
                _buf[_buf_pos]  = 0;
            }
        }

        char *p = (char *) strstr (_buf, GDB_PROMPT);
        if (p) {
            done = true;
        }
        else {
            /* copy 到最后一行，如果全部 copy 的话，可能会出现这样的情
             * 况，就是这次读取 gdb 输出的时候，只读到了部份的提示符。
             */
            p = (char *) strrchr (_buf, '\n');

            if (p)
                ++p;
        }

        if (p)
            result.append(_buf, (int) (p - _buf));

        if (done) {
            p += GDB_PROMPT_LEN;

            while (CHAR_IS_WS(*p))
                ++p;
        }

        if (p) {
            int pending_size = (_buf + _buf_pos - p);

            memmove(_buf, p, pending_size);

            _buf_pos = pending_size;

            _buf[_buf_pos] = 0;
        }
    }

    return 0;
}

static int
_get_curr_thread_bt(Process& proc, std::string& result)
{
    std::string buf;

    proc.write_stdin("bt\n");

    _get_debugger_output(proc, buf);

    result += buf;

    return 0;
}

static int
_get_threads(Process& proc, Ints& threads)
{
    std::string buf;

    proc.write_stdin("info threads\n");

    _get_debugger_output(proc, buf);

    const char *p, *nl;

    p = (const char *) buf.c_str();

    while ((nl = strchr(p, '\n')) != NULL) {
        // skip white-space
        while (CHAR_IS_WS(*p) || *p == '*')
            ++p;
        if (CHAR_IS_DIG(*p)) {
            int cnt = 0;
            while (CHAR_IS_DIG(*p)) {
                cnt *= 10;
                cnt += *p - '0';
                ++p;
            }
            threads.push_back(cnt);
        }
        else {
            std::string tmp;
            tmp.assign(p, nl - p);
            if (tmp.size() > 0) {
                log_print("_get_threads: warning, invalid thread line:\n"
                          " `%s'\n", tmp.c_str());
            }
        }

        p = nl + 1;
    }

    return 0;
}

/* 这个函数是可以在信号处理函数中调用的，其中系统调用有：
 * getpid
 * pipe
 * fork
 * close
 * waitpid
 * read
 * write
 * 这些函数都是可以调用的可重入函数
 */

int dbg_get_bt(std::string& result, int thread_type)
{
    Process     proc;
    Strings     args;
    std::string pid;
    std::string buf;
    std::string fmt;
    pid_t       child;
    int         pfds[2];

    /* 因为要得到当前进程的 back-trace，所以要在另外一个进程里调用 gdb
     * 来 attach 当前进程。
     */

    str_append(pid, getpid());

    if (pipe(pfds) < 0) {
        log_print("dbg_get_bt: pipe failed: %s\n", strerror(errno));
        return -1;
    }

    child = fork();

    if (child < 0) {
        log_print("dbg_get_bt: fork failed: %s\n", strerror(errno));
        close(pfds[0]);
        close(pfds[1]);
        return -1;
    }
    /* 子进程 */
    else if (child == 0) {
        /* 写端 */
        int fd = pfds[1];
        close(pfds[0]);

        args.push_back("--pid");
        args.push_back(pid);

        int sin, sout;
        sin = sout = PROC_BIND_PIPE;
        if (!proc.run("gdb", args, &sin, &sout))
            return -1;

        /* 忽略欢迎输出
         */
        _get_debugger_output(proc, buf);

        if (thread_type == DBG_TT_ALL_THREAD) {
            Ints threads;
            _get_threads(proc, threads);

            FOREACH(int i, threads) {
                /* 切换到相应的线程
                 */
                fmt.clear();
                str_format(fmt, "thread %d\n", i);
                proc.write_stdin(fmt.c_str());

                /* 忽略切换输出
                 */
                buf.clear();
                _get_debugger_output(proc, buf);

                str_format_append(result, "\nthread %d:\n", i);
                _get_curr_thread_bt(proc, result);
            }
        }
        else {
            result += "current thread:\n";
            _get_curr_thread_bt(proc, result);
        }

        if (result.size() > 0) {
            int cur = 0;
            int len = (int) result.size();
            const char *p = result.c_str();

            while (cur < len) {
                int r = write(fd, p + cur, len - cur);

                if (r > 0)
                    cur += r;
                else if (r < 0)
                    if (errno != EINTR && errno != EAGAIN)
                        break;
            }
        }

        close(fd);

        /* 结束 gdb */
        proc.write_stdin("detach\n");
        buf.clear();
        _get_debugger_output(proc, buf);

        /* 退出命令没有返回信息 */
        proc.write_stdin("quit\n");

        int exited;
        int exit_type;
        int exit_value;

        proc.wait_proc(PROC_WAIT, &exited, &exit_type, &exit_value);

        exit(0);
    }
    else {
        /* 等待子进程退出
         */
        int   status;
        pid_t id;
        int   fd;

        /* 读端 */
        fd = pfds[0];
        close(pfds[1]);

        id = waitpid(child, &status, 0);

        if (id < 0) {
            log_print("dbg_get_bt: waitpid failed: %s\n", strerror(errno));
            close(fd);
            return -1;
        }

        char tmp[4096];
        int  r;
        for (;;) {
            r = read(fd, tmp, 4096);
            if (r > 0)
                result.append(tmp, r);
            else if (r == 0)
                break;
            else if (r < 0 && errno != EINTR && errno != EAGAIN) {
                log_print("dbg_get_bt: read result failed: %s\n", strerror(errno));
                close(fd);
                return -1;
            }
        }

        close(fd);
    }

    return 0;
}

static dbg_fatal_func_t _fatal_func = NULL;

static const char *
_signo_to_str(int signo)
{
    switch (signo) {
    case SIGSEGV:       return "SIGSEGV";
    case SIGABRT:       return "SIGABRT";
    case SIGFPE:        return "SIGFPE";
    }
    return "UNKNOW";
}

static void
_fatal_signal_handler(int signo)
{
    std::string bt;
    dbg_get_bt(bt, DBG_TT_ALL_THREAD);

    log_print("signal %d (%s) catch, back trace is:\n%s\n",
              signo, _signo_to_str(signo),
              bt.empty() ? "[empty]" : bt.c_str());

    if (_fatal_func)
        _fatal_func();

    log_print("program exit...\n");

    exit(11);
}

int dbg_handle_fatal_signals(dbg_fatal_func_t func)
{
    int signals[] = { SIGSEGV, SIGABRT, SIGFPE, 0 };

    int i;
    for (i = 0; signals[i]; i++) {
        mysignal(signals[i], _fatal_signal_handler);
    }

    _fatal_func = func;

    return 0;
}
